<template>
  <div class="graph-model">
    <!-- 功能按钮  -->
    <div class="header">
      <div class="title">网络构建</div>
      <div class="tool-list">
        <a-tooltip placement="top">
          <span slot="title">后退</span>
          <span
            class="graph-model-btn"
            :class="{
              'no-have-stack': undoIconStatus,
              'prohibit-edit': !isGraphSelfCreate,
            }"
            @click="modelUndo"
          >
            <a-icon-font type="iconhoutui2" />
          </span>
        </a-tooltip>
        <a-tooltip placement="top">
          <span slot="title">前进</span>
          <span
            class="graph-model-btn"
            :class="{
              'no-have-stack': redoIconStatus,
              'prohibit-edit': !isGraphSelfCreate,
            }"
            @click="modelRedo"
          >
            <a-icon-font type="iconqianjin1" />
          </span>
        </a-tooltip>
        <!-- 框选 / 套索 -->
        <!--        <a-tooltip placement="top">-->
        <!--          <span slot="title">{{brushSelectRadio?'取消框选':'框选'}}</span>-->
        <!--          <span class="graph-model-btn" :class="{'active-current-function': brushSelectRadio}" @click="brushSelect">-->
        <!--            <a-icon-font type="icontaosuo1"/>-->
        <!--          </span>-->
        <!--        </a-tooltip>-->
        <a-tooltip placement="top">
          <span slot="title">删除</span>
          <span
            class="graph-model-btn"
            :class="{ 'prohibit-edit': !isGraphSelfCreate }"
            @click="deleteNodeEdge"
          >
            <a-icon-font type="iconicon-beifen5" />
          </span>
        </a-tooltip>
        <span class="divider"></span>
        <a-tooltip placement="top">
          <span slot="title">构建/加载数据</span>
          <span
            class="graph-model-btn"
            :class="{ 'prohibit-edit': !isGraphSelfCreate }"
            @click="reloadData"
          >
            <a-icon-font
              v-show="isBuildingGraph"
              type="iconMySQLlianjieceshi"
            />
            <a-icon-font v-show="!isBuildingGraph" type="iconyunhang" />
          </span>
        </a-tooltip>
      </div>
    </div>
    <div
      id="graphModelContainer"
      ref="graphModelContainer"
      class="graphModelContainer"
      @drop="createNode"
    >
      <!-- 伪造 g6 tooltip 添加过节点属性后显示 -->
      <div
        v-if="addAttributeTooltip"
        ref="addAttributeTooltipRef"
        class="add-attribute-prompts"
        :style="{ left: nodeTooltipInfo.x, top: nodeTooltipInfo.y }"
      >
        <ul class="node-edge-tooltip">
          <li class="tooltip-splider">
            {{ nodeTooltipInfo.keyAttr.column }}：{{
              nodeTooltipInfo.value
            }}条记录
          </li>
          <li class="tooltip-splider">节点属性</li>
          <li class="current-tag">
            {{ nodeTooltipInfo.keyAttr.column }}
            <img
              v-if="
                attributeIsEqual(
                  nodeTooltipInfo.originLabel,
                  nodeTooltipInfo.keyAttr
                )
              "
              class="tag-node-img"
              :src="`${publicPath}/icon/tag-node.svg`"
            />
          </li>
          <template v-for="attr in nodeTooltipInfo.attrs">
            <li
              :key="attr.column"
              class="current-tag"
              :class="{
                'add-current-attr': attributeIsEqual(
                  nodeTooltipInfo.currentAdd,
                  attr
                ),
              }"
            >
              {{ attr.column }}
              <img
                v-if="attributeIsEqual(nodeTooltipInfo.originLabel, attr)"
                class="tag-node-img"
                :src="`${publicPath}/icon/tag-node.svg`"
              />
            </li>
          </template>
        </ul>
      </div>
    </div>
    <!-- node label 编辑 -->
    <div
      v-if="labelEditShow"
      class="label-edit"
      :style="{
        left: nodeRenameInputPosition.x + 'px',
        top: nodeRenameInputPosition.y + 'px',
        width: '120px',
      }"
    >
      <a-input
        v-model="labelEdit"
        v-focus
        type="text"
        size="small"
        @keyup.enter.native="$event.target.blur"
        @blur="editNodeNameEnter"
      />
    </div>

    <!-- edge label 编辑 -->
    <div
      v-if="edgeLabelEditShow"
      class="label-edit"
      :style="{
        left: edgeRenameInputPosition.x + 'px',
        top: edgeRenameInputPosition.y + 'px',
        width: '120px',
      }"
    >
      <a-input
        v-model="edgeLabelEdit"
        v-focus
        type="text"
        size="small"
        @keyup.enter.native="$event.target.blur"
        @blur="editEdgeNameEnter"
      />
    </div>

    <!-- 边权重配置 -->
    <div
      v-if="edgeWeightSetShow"
      class="edge-attribute-set"
      :style="{
        left: edgeWeightPosition.x + 'px',
        top: edgeWeightPosition.y + 'px',
      }"
    >
      <a-radio-group
        :default-value="edgeSelectedWeight"
        class="edge-attribute-set-content scroll-y"
        @change="onWeightChange"
      >
        <a-row class="node-attrs-checkbox" type="flex">
          <a-col :span="24">
            <a-radio value="null">无</a-radio>
          </a-col>
        </a-row>
        <div class="node-label-text">{{ sourceNode.label }}</div>
        <div class="node-attrs-checkbox">
          <div class="attrs-checkbox-content">
            <a-row type="flex">
              <a-col
                v-for="(item, index) in sourceNode.attrs"
                :key="index"
                :span="24"
              >
                <a-radio v-if="isNumericAttr(item)" :value="item"
                  >{{ JSON.parse(item).name || JSON.parse(item).column }}
                </a-radio>
              </a-col>
            </a-row>
          </div>
        </div>
        <div class="node-attrs-divider"></div>
        <div class="node-label-text">{{ targetNode.label }}</div>
        <div class="node-attrs-checkbox">
          <div class="attrs-checkbox-content">
            <a-row>
              <a-col
                v-for="(item, index) in targetNode.attrs"
                :key="index"
                :span="24"
              >
                <a-radio v-if="isNumericAttr(item)" :value="item"
                  >{{ JSON.parse(item).name || JSON.parse(item).column }}
                </a-radio>
              </a-col>
            </a-row>
          </div>
        </div>
      </a-radio-group>
    </div>

    <!-- 边属性配置 -->
    <div
      v-if="edgeSetShow"
      class="edge-attribute-set"
      :style="{
        left: edgeAttributePosition.x + 'px',
        top: edgeAttributePosition.y + 'px',
      }"
    >
      <div class="edge-attribute-set-content scroll-y">
        <div class="node-label-text">{{ sourceNode.label }}</div>
        <div class="node-attrs-checkbox">
          <div class="attrs-checkbox-content">
            <a-checkbox-group
              :default-value="edgeSelectedAttrs"
              @change="onSourceChange"
            >
              <a-row type="flex">
                <a-col
                  v-for="(item, index) in sourceNode.attrs"
                  :key="index"
                  :span="24"
                >
                  <a-checkbox :value="item"
                    >{{ JSON.parse(item).name || JSON.parse(item).column }}
                  </a-checkbox>
                </a-col>
              </a-row>
            </a-checkbox-group>
          </div>
        </div>
        <div class="node-attrs-divider"></div>
        <div class="node-label-text">{{ targetNode.label }}</div>
        <div class="node-attrs-checkbox">
          <div class="attrs-checkbox-content">
            <a-checkbox-group
              :default-value="edgeSelectedAttrs"
              @change="onTargetChange"
            >
              <a-row>
                <a-col
                  v-for="(item, index) in targetNode.attrs"
                  :key="index"
                  :span="24"
                >
                  <a-checkbox :value="item"
                    >{{ JSON.parse(item).name || JSON.parse(item).column }}
                  </a-checkbox>
                </a-col>
              </a-row>
            </a-checkbox-group>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
import G6 from '@antv/g6'
import {
  graphAddCategory,
  graphAddEdge,
  graphUpdateEdge,
  graphBatchDeleteCategoriesAndEdges,
  graphUpdateCategory,
  graphLoadGraphData,
  graphQueryById,
  graphUndo,
  graphRedo,
} from '@/api/visualization'
import GraphViewStore from '@/store/modules/graphview'
import GraphBuild from '@/store/modules/graphbuild'
import { cloneDeep, debounce } from 'lodash'
import {
  SwitchColor,
  colors,
  isObjectValueEqual,
  getDarkColor,
  getLightColor,
} from '@/components/graph/graph-util'
import { pipelineInstanceStatus } from '@/api/task'
import { publicPath } from '@/api/constants'
import noPermissionPrompt from '@/components/graph/user-permissions'

@Component({
  directives: {
    focus: {
      inserted: (element: any) => {
        element.focus()
      },
    },
  },
})
export default class GraphModel extends Vue {
  @Prop({ default: () => ({ nodes: [], edges: [] }) }) data: any
  public modelGraph: any
  // 颜色
  public colors = cloneDeep(colors)
  public selectNodeEdge: any = null //   选择的边或节点
  public isBuildingGraph: boolean = false
  public myFullInterval: any
  public graphDataSessionId: number = -1
  public currentGraphId: number = -1
  // public brushLassoSelectNodes: any = []  // 框选 / 套索 的节点,
  public ctrlSelectNodes: any = [] // ctrl 选择的节点,
  // public brushLassoSelectEdges: any = []  // 框选 / 套索 的边,
  public ctrlSelectEdges: any = [] // ctrl 选择的边,
  // public brushSelectRadio: boolean = false  // 框选开关
  // public lassoSelectRadio: boolean = false  // 套索开关

  private undoIconStatus: boolean = false
  private redoIconStatus: boolean = false
  private publicPath = publicPath

  // 是否没有编辑权限（图构建创建者）
  public get isGraphSelfCreate() {
    return GraphBuild.isGraphSelfCreate
  }

  private getNextColor() {
    // 已经存在的颜色
    const existingColor: any[] =
      this.modelGraph.cfg.nodes.length > 0
        ? this.modelGraph.cfg.nodes.map(
            (node: any) => node._cfg.model.style.fill
          )
        : []
    // 隔两位取色值， 如果24色值都存在， 随机取一色
    for (let i = 0; i < this.colors.length; i += 3) {
      const index = i % (this.colors.length - 1)
      if (!existingColor.includes(this.colors[index].color)) {
        return this.colors[index].color
      }
    }
    return this.colors[this.randomNum(24)].color
  }

  /**
   * 1-24 随机数  24 根据色彩数量来的
   */
  public randomNum(max: number) {
    return Math.floor(Math.random() * max) + 1
  }

  public getGraphId(): number {
    return Number.parseInt(localStorage.getItem('graphId') || '0', 10)
  }

  private updateColorStatus(color: string) {
    for (let i = 0; i < this.colors.length; i += 1) {
      if (this.colors[i].color === color) {
        this.colors[i].isInUse = false
        break
      }
    }
  }

  created() {
    // 重新定义一个节点类型，在节点中间显示文字
    G6.registerNode(
      'graph-model-node',
      {
        afterDraw: (cfg: any, group: any) => {
          group.addShape('text', {
            draggable: true,
            attrs: {
              textAlign: 'center',
              textBaseline: 'middle',
              y: 0,
              x: 0,
              text: cfg.value,
              fill: '#000',
              cursor: 'pointer',
            },
            name: 'node-value',
          })
        },
        update: (cfg: any, item: any) => {
          const group = item.getContainer()
          const nodeValue = group.find(
            (element: any) => element.get('name') === 'node-value'
          )
          nodeValue.attrs.text = cfg.value
        },
        afterUpdate: (cfg: any, item: any) => {
          const group = item.getContainer()
          const rightAnchor = group.find(
            (element: any) => element.get('className') === 'circle-anchor-right'
          )
          const leftAnchor = group.find(
            (element: any) => element.get('className') === 'circle-anchor-left'
          )
          const topAnchor = group.find(
            (element: any) => element.get('className') === 'circle-anchor-top'
          )
          const bottomAnchor = group.find(
            (element: any) =>
              element.get('className') === 'circle-anchor-bottom'
          )
          if (cfg.showLinks) {
            group.addShape('circle', {
              draggable: true,
              attrs: {
                stroke: '#5D637E',
                fill: '#5D637E',
                lineWidth: 10,
                strokeOpacity: 0,
                x: 30,
                y: 0,
                r: 5,
                cursor: 'ne-resize',
              },
              className: 'circle-anchor-right',
              name: 'circle-anchor',
            })

            group.addShape('circle', {
              draggable: true,
              attrs: {
                stroke: '#5D637E',
                fill: '#5D637E',
                lineWidth: 10,
                strokeOpacity: 0,
                x: -30,
                y: 0,
                r: 5,
                cursor: 'ne-resize',
              },
              className: 'circle-anchor-left',
              name: 'circle-anchor',
            })

            group.addShape('circle', {
              draggable: true,
              attrs: {
                stroke: '#5D637E',
                fill: '#5D637E',
                lineWidth: 10,
                strokeOpacity: 0,
                x: 0,
                y: 30,
                r: 5,
                cursor: 'ne-resize',
              },
              className: 'circle-anchor-top',
              name: 'circle-anchor',
            })

            group.addShape('circle', {
              draggable: true,
              attrs: {
                stroke: '#5D637E',
                fill: '#5D637E',
                lineWidth: 10,
                strokeOpacity: 0,
                x: 0,
                y: -30,
                r: 5,
                cursor: 'ne-resize',
              },
              className: 'circle-anchor-bottom',
              name: 'circle-anchor',
            })
          } else if (leftAnchor) {
            leftAnchor.remove()
            rightAnchor.remove()
            topAnchor.remove()
            bottomAnchor.remove()
          }
        },
      },
      'circle'
    )
  }

  // 生命周期 mounted
  public mounted() {
    document.addEventListener('keyup', this.globalKeyUp)
    // document.addEventListener('keydown',this.globalKeyDown)
  }

  public beforeDestroy() {
    document.removeEventListener('keyup', this.globalKeyUp)
    // document.removeEventListener('keydown', this.globalKeyDown)
  }

  public globalKeyDown($event: KeyboardEvent) {
    if ($event.key === 'Delete') {
      this.deleteNodeEdge()
    }
  }

  public globalKeyUp($event: KeyboardEvent) {
    if (this.isMac) {
      if ($event.shiftKey && $event.key === 'Backspace') {
        this.deleteNodeEdge()
      }
      return
    }
    if ($event.key === 'Delete') {
      this.deleteNodeEdge()
    }
  }

  public get isMac() {
    const { userAgent } = window.navigator
    return userAgent.includes('Mac OS X')
  }

  /**
   * undo
   */
  public async modelUndo() {
    if (!this.isGraphSelfCreate) {
      noPermissionPrompt()
      return
    }
    if (!this.undoIconStatus) {
      const response: any = await graphUndo({
        data: {
          graphId: this.getGraphId(),
        },
      })
      const { code } = response.data
      if (code === 100) {
        this.graphUndoRedo({
          result: response.data.result,
          type: 'undo',
        })
      } else if (code === 500) {
        this.$message.warning(response.data.tips)
      }
    }
  }

  /**
   * redo
   */
  public async modelRedo() {
    if (!this.isGraphSelfCreate) {
      noPermissionPrompt()
      return
    }
    if (!this.redoIconStatus) {
      const response: any = await graphRedo({
        data: {
          graphId: this.getGraphId(),
        },
      })
      const { code } = response.data
      if (code === 100) {
        this.graphUndoRedo({
          result: response.data.result,
          type: 'redo',
        })
      } else if (code === 500) {
        this.$message.warning(response.data.tips)
      }
    }
  }

  /**
   * 刷选操作
   */
  // public brushSelect() {
  //   this.brushSelectRadio = !this.brushSelectRadio
  //   const brushSelect: any = {
  //     type: 'brush-select',
  //     trigger: 'drag',
  //   }
  //   if(this.brushSelectRadio) {
  //     this.modelGraph.removeBehaviors('drag-canvas', 'default')
  //     this.modelGraph.addBehaviors(brushSelect, 'default')
  //   }else {
  //     this.modelGraph.removeBehaviors('brush-select', 'default')
  //     this.modelGraph.addBehaviors('drag-canvas', 'default')
  //   }
  // }

  /**
   * 删除选择的节点或边
   */
  public deleteNodeEdge(nodeEdge?: any) {
    if (!this.isGraphSelfCreate) {
      noPermissionPrompt()
      return
    }
    const { edgesId, nodesId } = this.collectDeletedElements(nodeEdge)
    if (nodesId.length > 0 || edgesId.length > 0) {
      this.removeNodeEdgeApi(edgesId, nodesId)
    }
  }

  /**
   * 批量删除节点 ， 边 的接口
   * @param edgeIds
   * @param nodeIds
   */
  public removeNodeEdgeApi(edgeIds: any, nodeIds: any) {
    // 调用接口批量删除边和节点
    return graphBatchDeleteCategoriesAndEdges({
      data: {
        graphId: this.getGraphId(),
        edgeIds,
        categoryIds: nodeIds,
      },
    }).then((response: any) => {
      if (response.data.code === 100) {
        // 移除节点
        nodeIds.forEach((id: any) => {
          this.modelGraph.removeItem(id)
        })
        // 移除边
        const graphEdges: any = cloneDeep(this.modelGraph.getEdges()) // 所有边
        edgeIds.forEach((id: any) => {
          // g6 给的id和自己的id (model.id) 不同步的问题
          const findEdge: any = graphEdges.find(
            (edge: any) => edge._cfg.model.id === id
          )
          if (findEdge?._cfg.id) {
            this.modelGraph.removeItem(findEdge._cfg.id)
          }
        })
        this.selectNodeEdge = null // 清空记录的选的节点或边
        this.ctrlSelectNodes = [] // 清空 ctrl 多选的节点
        this.ctrlSelectEdges = [] // 清空 ctrl 多选的边
        if (edgeIds.includes(GraphBuild.selectedEdge?.model.id)) {
          this.$emit('graphClick')
        }
        // 如果删除的有当前激活节点， 表数据显示源表
        if (nodeIds.includes(GraphViewStore.currentCategoryData?.id)) {
          GraphViewStore.setCurrentCategoryData(null)
          this.$emit('delete-current-category')
        }
      }
    })
  }

  /**
   * 搜集删除的元素
   */
  public collectDeletedElements(nodeEdge?: any) {
    // ctrl 多选的节点
    let ctrlSelectNodesId: any[] =
      this.ctrlSelectNodes.length > 0
        ? this.ctrlSelectNodes.map((node: any) => node._cfg.model.id)
        : []
    // 当前激活的节点， 放入
    if (this.selectNodeEdge?._cfg?.type === 'node') {
      ctrlSelectNodesId.push(this.selectNodeEdge._cfg.model.id)
    }
    // 当前右键的节点
    if (
      nodeEdge?._cfg?.type === 'node' &&
      !ctrlSelectNodesId.includes(nodeEdge._cfg.model.id)
    ) {
      ctrlSelectNodesId.push(nodeEdge._cfg.model.id)
    }
    ctrlSelectNodesId = [...new Set(ctrlSelectNodesId)] // 去重

    // 根据上述选择的节点 搜集的边
    const edgesId: any = []
    this.modelGraph.getEdges().forEach((edgeElement: any) => {
      const edge = edgeElement._cfg.model
      if (
        (ctrlSelectNodesId.includes(edge.source) ||
          ctrlSelectNodesId.includes(edge.target)) &&
        !edgesId.includes(edge.id)
      ) {
        edgesId.push(edge.id)
      }
    })
    // ctrl 多选的边
    const ctrlSelectEdgeIds: any =
      this.ctrlSelectEdges.length > 0
        ? this.ctrlSelectEdges.map((edge: any) => edge._cfg.model.id)
        : []
    // 当前激活的节点， 放入
    if (this.selectNodeEdge?._cfg?.type === 'edge') {
      ctrlSelectEdgeIds.push(this.selectNodeEdge._cfg.model.id)
    }
    // 当前右键的边
    if (
      nodeEdge?._cfg?.type === 'edge' &&
      !ctrlSelectEdgeIds.includes(nodeEdge._cfg.model.id)
    ) {
      ctrlSelectEdgeIds.push(nodeEdge._cfg.model.id)
    }
    // 合并 去重
    const concatEdgeIds: any = [...new Set(edgesId.concat(ctrlSelectEdgeIds))]
    return {
      edgesId: concatEdgeIds,
      nodesId: ctrlSelectNodesId,
    }
  }

  public reloadData() {
    if (!this.isGraphSelfCreate) {
      noPermissionPrompt()
      return
    }
    if (GraphViewStore.graphSourceTables.length <= 0) {
      return
    }

    this.isBuildingGraph = true
    // trigger to load graph data
    graphLoadGraphData({
      data: {
        graphId: this.getGraphId(),
      },
    }).then((response: any) => {
      ;[this.graphDataSessionId] = response.data.result
    })

    // loop check with pipeline status
    GraphBuild.setIsBatchSave(false) // 未存储， 禁止保存到可视化
    GraphBuild.setFirstSaveGraphAnalysis(true) // 点击加载， 用于第一次构建保存  构建分析g6 弃用
    GraphBuild.setGraphSaveStatus({
      type: 1,
      lastSaveTime: new Date().getTime(),
    })
    GraphBuild.setIsBuildingGraph(2) // 正在构建中
    this.reloadGraphDataStatusPoll()
  }

  /**
   * 全量数据 轮询
   */
  public reloadGraphDataStatusPoll() {
    clearInterval(this.myFullInterval)
    this.myFullInterval = setInterval(() => {
      this.checkGraphDataRunStatus()
    }, 1000)
  }

  /**
   * 全量数据的轮询
   */
  public async checkGraphDataRunStatus() {
    const response: any = await pipelineInstanceStatus({
      data: {
        sessionId: this.graphDataSessionId.toString(),
      },
    })
    if (response.data.code === 100) {
      const { status } = response.data.result.sessionLogs[0]
      if (status === 'SUCCESS') {
        clearInterval(this.myFullInterval)
        this.reloadAfterStatusCheckSuccess()
      }

      if (status === 'FAIL') {
        this.isBuildingGraph = false
        this.$message.error('构建失败， 请检查！')
        GraphBuild.setIsBuildingGraph(4) // 构建结束
        clearInterval(this.myFullInterval)
      }
    }
  }

  /**
   * reaload data after pipeline status check success
   */
  public reloadAfterStatusCheckSuccess() {
    graphQueryById({
      data: {
        graphId: this.getGraphId(),
      },
    }).then((response: any) => {
      const { result } = response.data
      this.isBuildingGraph = false
      // GraphBuild.setFirstSaveGraphAnalysis(true)  // 移动位置
      this.$emit('reloadData', result)
    })
  }

  /**
   * 创建节点
   */
  public createNode(event: any) {
    event.stopPropagation() // firefox bug - Drag ends to open a new window
    event.preventDefault()
    if (!this.isGraphSelfCreate) {
      noPermissionPrompt()
      return
    }
    const newNodeData = event.dataTransfer.getData('text/plain').split(':')
    const nodeColor: string = this.getNextColor()
    const tableInfo = GraphBuild.getTableData.find((table: any) => {
      return table.tableName === newNodeData[1]
    })
    const newNode = {
      label: newNodeData[0],
      originLabel: {
        column: newNodeData[0],
        name: newNodeData[0],
        type: newNodeData[2],
      },
      x: event.offsetX,
      y: event.offsetY,
      attrs: [],
      keyAttr: {
        column: newNodeData[0],
        name: newNodeData[0],
        type: newNodeData[2],
      },
      style: {
        fill: nodeColor,
        stroke: nodeColor,
        lineWidth: 4,
      },
      size: 60,
      labelCfg: {
        position: 'bottom',
      },
      tableInfo,
    }

    graphAddCategory({
      data: {
        graphId: this.getGraphId(),
        category: newNode,
      },
    }).then((response: any) => {
      const nodeData = response.data.result
      // 入操作栈 添加节点
      this.modelGraph.addItem('node', {
        ...nodeData,
        type: 'graph-model-node',
      })
    })
  }

  /**
   * 切换节点标签 way === 1
   * @params node 要修改的当前节点
   * @params dataset
   */
  public switchNodeLabel(node: any, dataset: any) {
    const originLabel = {
      column: dataset.column,
      name: dataset.name ? dataset.name : null,
      type: Number(dataset.type),
      id: dataset.id,
    }
    const label: string = originLabel.name
      ? originLabel.name
      : originLabel.column
    const modelItem: any = cloneDeep(node._cfg.model)
    modelItem.originLabel = originLabel
    modelItem.label = originLabel.name || originLabel.column
    modelItem.style.stroke = modelItem.style.fill

    graphUpdateCategory({
      data: {
        graphId: this.getGraphId(),
        category: {
          ...modelItem,
        },
      },
    }).then((response: any) => {
      if (response.data.code === 100) {
        this.modelGraph.updateItem(node, {
          originLabel,
          label,
        })
        this.modelGraph.render()
      }
    })
  }

  /**
   * 修改节点颜色 way === 2
   * @params element 要修改的当前节点
   * @params color 颜色
   */
  public setNodeColor(element: any, color: string) {
    const modelItem = cloneDeep(element._cfg.model)
    if (color === modelItem.style.fill) {
      return
    }
    const style: any = { fill: color, stroke: color }
    modelItem.style.fill = color
    modelItem.style.stroke = color
    graphUpdateCategory({
      data: {
        graphId: this.getGraphId(),
        category: {
          ...modelItem,
        },
      },
    }).then((response: any) => {
      if (response.data.code === 100) {
        this.modelGraph.updateItem(element, {
          style,
        })
        this.modelGraph.render()
      }
    })
  }

  // node 节点重命名
  private labelEdit: string = '' // 节点 label 编辑
  private labelEditShow: boolean = false // 节点 label 编辑 输入框
  private currentEditNode: any = null // 当前重命名的节点
  private nodeRenameInputPosition: { x: number; y: number } = { x: 20, y: 45 } // 节点重命名输入框位置
  /**
   * node 节点重命名 way === 3
   */
  public nodeRename(element: any) {
    const { model } = element._cfg
    // this.modelGraph.findById(newEdge.source)
    this.nodeRenameInputPosition.x = model.x - 60
    this.nodeRenameInputPosition.y = model.y + 70
    this.labelEdit = model.label
    this.labelEditShow = true
    this.currentEditNode = element
  }

  /**
   * 输入框失焦
   */
  private editNodeNameEnter() {
    const modelItem = cloneDeep(this.currentEditNode._cfg.model)
    if (modelItem.label !== this.labelEdit) {
      modelItem.label = this.labelEdit
      // 避免保存 hover 状态的样式
      modelItem.style.stroke = modelItem.style.fill

      graphUpdateCategory({
        data: {
          graphId: this.getGraphId(),
          category: {
            ...modelItem,
          },
        },
      }).then((response: any) => {
        this.labelEditShow = false
        if (response.data.code === 100) {
          this.modelGraph.updateItem(this.currentEditNode, {
            label: this.labelEdit,
          })
          this.modelGraph.render()
        }
      })
    } else {
      this.labelEditShow = false
    }
  }

  /**
   * 节点 menu 事件
   * target.dataset.way  1-切换标签 2-颜色设置 3-重命名 4-复制 5-删除
   */
  public nodeMenuClick(target: any, item: any) {
    if (target.dataset.way === '1') {
      this.switchNodeLabel(
        this.modelGraph.findById(item._cfg.id),
        target.dataset
      )
    } else if (target.dataset.way === '2') {
      // 修改颜色设置
      const element = this.modelGraph.findById(item._cfg.id)
      this.setNodeColor(element, target.dataset.color)
    } else if (target.dataset.way === '3') {
      const element = this.modelGraph.findById(item._cfg.id)
      this.nodeRename(element)
    } else if (target.dataset.way === '4') {
      console.log(target.dataset, target, item, '-----复制------')
    } else if (target.dataset.way === '5') {
      if (item._cfg.type === 'node') {
        this.updateColorStatus(item._cfg.originStyle.fill)
      }
      const element = this.modelGraph.findById(item._cfg.id)
      // 删除节点
      this.deleteNodeEdge(element)
    }
  }

  /**  ===== node 节点的 menu 向上 ======  */
  private contextMenu = new G6.Menu({
    getContent($event: any) {
      const { model } = $event.item._cfg
      // 切换的标签
      let switchList = ''
      if (isObjectValueEqual(model.originLabel, model.keyAttr)) {
        switchList += `<li class="padding-left-eighteen current-status" data-way="1" data-column="${
          model.keyAttr.column
        }" data-type="${model.keyAttr.type}" data-name="${
          model.keyAttr.name || ''
        }">${model.keyAttr.name || model.keyAttr.column}</li>`
      } else {
        switchList += `<li class="padding-left-eighteen" data-way="1" data-column="${
          model.keyAttr.column
        }" data-type="${model.keyAttr.type}" data-name="${
          model.keyAttr.name || ''
        }">${model.keyAttr.name || model.keyAttr.column}</li>`
      }
      model.attrs.forEach((item: any) => {
        if (isObjectValueEqual(model.originLabel, item)) {
          switchList += `<li class="padding-left-eighteen current-status" data-way="1" data-column="${
            item.column
          }" data-type="${item.type}" data-name="${item.name || ''}" data-id="${
            item.id
          }">${item.name || item.column}</li>`
        } else {
          switchList += `<li class="padding-left-eighteen" data-way="1" data-column="${
            item.column
          }" data-type="${item.type}" data-name="${item.name || ''}">${
            item.name || item.column
          }</li>`
        }
      })
      // 设置颜色
      let setColorDOM = ''
      SwitchColor.forEach((item: any) => {
        setColorDOM += `<li class="color-item" data-way="2" data-color="${item.color}" style="background-color: ${item.color}; border-color: ${item.border}"></li>`
      })

      const outDiv = document.createElement('div')
      outDiv.style.width = '120px'
      outDiv.innerHTML = `<ul class="node-edge-menu">
                    <li class="sub-menu-item have-sub right-triangle">
                        <span>切换标签</span>
                        <ul class="sub-menu-section switching-current-item">
                            ${switchList}
                        </ul>
                    </li>
                    <li class="first-divider"></li>
                    <li class="sub-menu-item have-sub right-triangle">
                        <span>设置颜色</span>
                        <div class="sub-menu-section set-color">
                          <ul class="all-color">
                            ${setColorDOM}
                          </ul>
                        </div>

                    </li>
                    <li data-way="3" class="sub-menu-item">重命名</li>
                    <li data-way="5" class="sub-menu-item">删除</li>
                    </ul>`
      // <li data-way="4" class="sub-menu-item">复制</li> 复制降低优先级
      return outDiv
    },
    handleMenuClick: this.nodeMenuClick,
    shouldBegin: this.shouldNode,
    offsetX: 16 + 10,
    offsetY: 0,
    itemTypes: ['node'],
  })

  /**
   * node 右键 menu 前置判断处理
   */
  public shouldNode($event: any) {
    if (!this.isGraphSelfCreate) {
      return false
    }
    // ctrl 引发
    return !$event.originalEvent.ctrlKey
  }

  /**  ===================== edge 边的 menu 向下 start ============================ */
  private edgeContextMenu = new G6.Menu({
    // @ts-ignore
    getContent($event: any, graph: any) {
      const { model } = $event.item._cfg
      // 设置颜色
      let setColorDOM = ''
      SwitchColor.forEach((item: any) => {
        setColorDOM += `<li class="color-item" data-way="2" data-color="${item.color}" style="background-color: ${item.color}; border-color: ${item.border}"></li>`
      })

      const sourceEle = graph.findById(model.source)
      const targetEle = graph.findById(model.target)

      const sourceName = sourceEle._cfg.model.label
      const targetName = targetEle._cfg.model.label

      // 方向设置
      let directionSetDom = ''
      // 有方向
      if (model.style.endArrow === true) {
        directionSetDom = `
          <li data-way="3" data-direction="0" class="padding-left-eighteen">无方向</li>
          <li data-way="3" data-direction="1" class="padding-left-eighteen current-status" title="${sourceName}->${targetName}">${sourceName}->${targetName}</li>
          <li data-way="3" data-direction="2" class="padding-left-eighteen" title="${targetName}->${sourceName}">${targetName}->${sourceName}</li>
        `
      } else {
        directionSetDom = `
          <li data-way="3" data-direction="0" class="padding-left-eighteen current-status">无方向</li>
          <li data-way="3" data-direction="1" class="padding-left-eighteen" title="${sourceName}->${targetName}">${sourceName}->${targetName}</li>
          <li data-way="3" data-direction="2" class="padding-left-eighteen" title="${targetName}->${sourceName}">${targetName}->${sourceName}</li>
        `
      }
      const outDiv = document.createElement('div')
      outDiv.style.width = '120px'
      outDiv.innerHTML = `<ul class="node-edge-menu">
                    <li data-way="0" class="sub-menu-item have-sub">
                        <span data-way="0">边权重配置</span>
                    </li>
                    <li data-way="1" class="sub-menu-item have-sub">
                        <span data-way="1">边属性配置</span>
                    </li>
                    <li class="first-divider"></li>
                    <li class="sub-menu-item have-sub right-triangle">
                        <span>设置颜色</span>
                        <div class="sub-menu-section set-color">
                          <ul class="all-color">
                            ${setColorDOM}
                          </ul>
                        </div>
                    </li>
                    <li class="sub-menu-item have-sub right-triangle">
                        <span>方向设置</span>
                        <ul class="sub-menu-section switching-current-item">
                          ${directionSetDom}
                        </ul>
                    </li>
                    <li data-way="4" class="sub-menu-item">重命名</li>
                    <li data-way="5" class="sub-menu-item">删除</li>
                    </ul>`
      return outDiv
    },
    handleMenuClick: this.edgeMenuClick,
    offsetX: 16 + 10,
    offsetY: 0,
    shouldBegin: this.shouldEdge,
    itemTypes: ['edge'],
  })

  /**
   * edge 右键 menu 前置判断处理
   */
  public shouldEdge($event: any) {
    if (!this.isGraphSelfCreate) {
      return false
    }
    // ctrl 引发
    if ($event.originalEvent.ctrlKey) {
      return false
    }
    this.edgeSetShow = false
    return true
  }

  /**
   * edge 边menu 事件
   * target.dataset.way 0-边权重设置 1-边属性设置 2-颜色设置 3-方向设置 4-重命名 5-删除
   */
  public edgeMenuClick(target: any, item: any) {
    if (target.dataset.way === '0') {
      const element = this.modelGraph.findById(item._cfg.id)
      this.edgeWeightSet(element)
    } else if (target.dataset.way === '1') {
      const element = this.modelGraph.findById(item._cfg.id)
      this.edgeAttributeSet(element)
    } else if (target.dataset.way === '2') {
      const element = this.modelGraph.findById(item._cfg.id)
      this.setEdgeColor(element, target.dataset.color)
    } else if (target.dataset.way === '3') {
      const element = this.modelGraph.findById(item._cfg.id)
      this.setDirection(element, target)
    } else if (target.dataset.way === '4') {
      const element = this.modelGraph.findById(item._cfg.id)
      this.edgeRename(element)
    } else if (target.dataset.way === '5') {
      if (item._cfg.type === 'node') {
        this.updateColorStatus(item._cfg.originStyle.fill)
      }
      const element = this.modelGraph.findById(item._cfg.id)
      // 删除边
      this.deleteNodeEdge(element)
    }
  }

  // edge 边权重设置 way === 0
  private edgeSelectedWeight: any = {} // 当前边已选择的权重
  private edgeWeightSetShow: boolean = false // 边属性设置 显示
  private edgeWeightPosition: { x: number; y: number } = { x: 20, y: 45 } // 边的权重设置radio的位置

  /**
   *  边权重设置way === 0
   */
  public edgeWeightSet(edge: any) {
    this.currentSetEdge = edge // 记录当前边
    const { model } = edge._cfg
    this.edgeWeightPosition.x = (model.startPoint.x + model.endPoint.x) / 2 + 30
    this.edgeWeightPosition.y = (model.startPoint.y + model.endPoint.y) / 2 + 50
    // 开始节点 信息 label attrs
    const source: any = this.modelGraph.findById(model.source)

    this.sourceNode.label = source._cfg.model.label
    this.sourceNode.attrs = source._cfg.model.attrs
      .concat([source._cfg.model.keyAttr])
      .map((item: any) => JSON.stringify(item))
    // 结束节点 信息 label attrs
    const target: any = this.modelGraph.findById(model.target)
    this.targetNode.label = target._cfg.model.label
    this.targetNode.attrs = target._cfg.model.attrs
      .concat([target._cfg.model.keyAttr])
      .map((item: any) => JSON.stringify(item))
    this.edgeSelectedWeight = edge._cfg.model.weightCfg
      ? edge._cfg.model.weightCfg
      : 'null' // 已经设置的权重

    // 如果配置不为空，那么在源节点和目标节点中找到相应的属性
    if (this.edgeSelectedWeight.attrId !== 'null') {
      // 在源节点中查找是否包含指定id的属性
      const selectedAttribute = this.findAttrById(
        this.sourceNode.attrs,
        this.edgeSelectedWeight.attrId
      )
      if (selectedAttribute === 'null') {
        // 在目标节点中查找是否包含指定id的属性
        this.edgeSelectedWeight = this.findAttrById(
          this.targetNode.attrs,
          this.edgeSelectedWeight.attrId
        )
      } else {
        this.edgeSelectedWeight = selectedAttribute
      }
    }

    this.edgeWeightSetShow = true // 已选择的属性配置 复现
  }

  // 在属性列表中，根据id来查找相应的属性
  public findAttrById(attributes: string[], attributeId: string) {
    // eslint-disable-next-line no-restricted-syntax
    for (const attribute of attributes) {
      if (attribute.includes(attributeId)) {
        return attribute
      }
    }
    return 'null'
  }

  // 是否为数值属性
  public isNumericAttr(attribute: string) {
    const numericAttributeTypes = [-6, -5, 2, 3, 4, 5, 6, 7, 8]
    const { type } = JSON.parse(attribute)
    return numericAttributeTypes.includes(type)
  }

  public onWeightChange($event: any) {
    this.edgeSelectedWeight = $event.target.value
  }

  // 边的权重设置
  @Watch('edgeWeightSetShow')
  public edgeWeightSetChange(value: boolean) {
    if (!value) {
      const weightCfg = {
        attrId:
          this.edgeSelectedWeight !== 'null'
            ? JSON.parse(this.edgeSelectedWeight).id
            : null,
      }
      const modelItem = cloneDeep(this.currentSetEdge._cfg.model)
      modelItem.weightCfg = weightCfg
      const data = {
        graphId: this.getGraphId(),
        edge: {
          ...modelItem,
        },
      }
      graphUpdateEdge({
        data,
      }).then((response: any) => {
        if (response.data.code === 100) {
          this.modelGraph.updateItem(this.currentSetEdge, {
            weightCfg, // 边权重
          })
        }
      })
    }
  }

  // edge 边属性设置 way === 1
  private currentSetEdge: any = null // 设置属性的当前边
  private sourceNode: any = { attrs: [], label: '' } // 当前 边属性设置 的边 开始节点 attrs: string[]
  private targetNode: any = { attrs: [], label: '' } // 当前 边属性设置 的边 结束节点 attrs: string[]
  private edgeSelectedAttrs: any = [] // 当前边已选择的属性
  private edgeAttributePosition: { x: number; y: number } = { x: 20, y: 45 } // 边的属性设置checkbox位置
  private edgeSetShow: boolean = false // 边属性设置 显示

  /**
   * 边属性设置 way === 1
   */
  public edgeAttributeSet(edge: any) {
    this.currentSetEdge = edge // 记录当前边
    const { model } = edge._cfg
    this.edgeAttributePosition.x =
      (model.startPoint.x + model.endPoint.x) / 2 + 30
    this.edgeAttributePosition.y =
      (model.startPoint.y + model.endPoint.y) / 2 + 50
    // 开始节点 信息 label attrs
    const source: any = this.modelGraph.findById(model.source)
    this.sourceNode.label = source._cfg.model.label
    this.sourceNode.attrs = source._cfg.model.attrs
      .concat([source._cfg.model.keyAttr])
      .map((item: any) => JSON.stringify(item))
    // 结束节点 信息 label attrs
    const target: any = this.modelGraph.findById(model.target)
    this.targetNode.label = target._cfg.model.label
    this.targetNode.attrs = target._cfg.model.attrs
      .concat([target._cfg.model.keyAttr])
      .map((item: any) => JSON.stringify(item))

    this.edgeSelectedAttrs = edge._cfg.model.attrIds
      ? edge._cfg.model.attrIds.map((item: any) => {
          // 在源节点中查找是否包含指定id的属性
          const selectedAttribute = this.findAttrById(
            this.sourceNode.attrs,
            item
          )
          if (selectedAttribute === 'null') {
            // 在目标节点中查找是否包含指定id的属性
            return this.findAttrById(this.targetNode.attrs, item)
          }
          return selectedAttribute
        })
      : [] // 已经设置的属性

    this.edgeSetShow = true // 已选择的属性配置 复现
  }

  public updateAttrs: any = [] // 选中的属性
  public sourceAttrs: any = [] // 开始节点选择的 属性
  public targetAttrs: any = [] // 结束节点选择的 属性

  // 开始节点 属性选择框
  private onSourceChange($event: any) {
    this.sourceAttrs = $event
    this.updateAttrs = $event.concat(this.targetAttrs)
  }

  // 边的结束节点 属性选择框
  private onTargetChange($event: any) {
    this.targetAttrs = $event
    this.updateAttrs = $event.concat(this.sourceAttrs)
  }

  // 边的属性设置
  @Watch('edgeSetShow')
  public edgeAttrsSetChange(value: boolean) {
    if (!value) {
      const attributesIds: any = this.updateAttrs?.map((item: any) => {
        return JSON.parse(item).id
      })
      const modelItem = cloneDeep(this.currentSetEdge._cfg.model)
      modelItem.attrIds = attributesIds
      const data = {
        graphId: this.getGraphId(),
        edge: {
          ...modelItem,
        },
      }
      graphUpdateEdge({
        data,
      }).then((response: any) => {
        if (response.data.code === 100) {
          this.modelGraph.updateItem(this.currentSetEdge, {
            attrIds: attributesIds, // 边属性
          })
        }
      })
    }
  }

  /**
   * 修改 edge 边颜色  way === 2
   * @params element 要修改的当前边
   * @params color 颜色
   */
  public setEdgeColor(element: any, color: string) {
    const modelItem = cloneDeep(element._cfg.model)
    modelItem.style.stroke = color
    modelItem.style.endArrow = modelItem.style.endArrow === true
    graphUpdateEdge({
      data: {
        graphId: this.getGraphId(),
        edge: {
          ...modelItem,
        },
      },
    }).then((response: any) => {
      if (response.data.code === 100) {
        this.modelGraph.updateItem(element, {
          style: {
            stroke: color,
          },
        })
      }
    })
  }

  /**
   * edge 边 方向设置 way === 3
   */
  public setDirection(element: any, target: any) {
    const modelItem = cloneDeep(element.getModel())
    const { direction } = target.dataset
    if (direction === '0') {
      modelItem.style.endArrow = false
    } else if (direction === '1') {
      modelItem.style.endArrow = true
    } else if (direction === '2') {
      // 切换方向
      const { source } = modelItem
      // 存在一条与切换方向后相同的边
      const existedEdge: any = this.modelGraph.find('edge', (edge: any) => {
        const edgeModel: any = edge.getModel()
        return (
          edgeModel.source === modelItem.target &&
          edgeModel.target === modelItem.source
        )
      })
      if (existedEdge) {
        this.$message.warning('目标方向已存在另外一条边，无法修改！')
        return
      }
      modelItem.source = modelItem.target
      modelItem.target = source
      modelItem.style.endArrow = true
    }
    graphUpdateEdge({
      data: {
        graphId: this.getGraphId(),
        edge: {
          ...modelItem,
        },
      },
    }).then((response: any) => {
      this.edgeLabelEditShow = false
      if (response.data.code === 100) {
        this.modelGraph.updateItem(element, {
          source: modelItem.source,
          target: modelItem.target,
          style: {
            endArrow: modelItem.style.endArrow,
          },
        })
      }
    })
  }

  // edge 边重命名 way === 4
  private currentEditEdge: any = null // 当前重命名的边 edge
  private edgeLabelEdit: string = '' // edge 边 label 编辑
  private edgeLabelEditShow: boolean = false // edge label 编辑 输入框
  private edgeRenameInputPosition: { x: number; y: number } = { x: 20, y: 45 } // 节点重命名输入框位置
  /**
   *  edge 重命名 way === 4
   */
  public edgeRename(element: any) {
    const { model } = element._cfg
    this.edgeLabelEdit = model.label
    // this.modelGraph.findById(newEdge.source)
    this.edgeRenameInputPosition.x =
      (model.startPoint.x + model.endPoint.x) / 2 - 60
    this.edgeRenameInputPosition.y =
      (model.startPoint.y + model.endPoint.y) / 2 + 27
    this.edgeLabelEditShow = true
    this.currentEditEdge = element
  }

  /**
   * 边 重命名输入框失焦
   */
  private editEdgeNameEnter() {
    const modelItem = cloneDeep(this.currentEditEdge._cfg.model)
    if (modelItem.label !== this.edgeLabelEdit) {
      modelItem.label = this.edgeLabelEdit
      graphUpdateEdge({
        data: {
          graphId: this.getGraphId(),
          edge: {
            ...modelItem,
          },
        },
      }).then((response: any) => {
        this.edgeLabelEditShow = false
        if (response.data.code === 100) {
          this.modelGraph.updateItem(this.currentEditEdge, {
            label: this.edgeLabelEdit,
          })
        }
      })
    } else {
      this.edgeLabelEditShow = false
    }
  }

  /**  ===================== edge 边的 menu  end ============================ */

  // tooltip
  private nodeTooltip = new G6.Tooltip({
    itemTypes: ['node'],
    offsetX: 25,
    offsetY: 60,
    getContent: (event: any) => {
      // 节点属性 tooltip DOM
      const model: any = event.item.getModel()
      const tagDOM: any = `<img class="tag-node-img" src="${this.publicPath}/icon/tag-node.svg"/>`
      let nodeAttributesDOM: string = ''
      if (isObjectValueEqual(model.originLabel, model.keyAttr)) {
        nodeAttributesDOM += `<li class="current-tag">${
          event.item.getModel().keyAttr.column
        }${tagDOM}</li>`
      } else {
        nodeAttributesDOM += `<li>${event.item.getModel().keyAttr.column}</li>`
      }
      model.attrs?.forEach((item: any) => {
        if (isObjectValueEqual(model.originLabel, item)) {
          nodeAttributesDOM += `<li class="current-tag">${item.column}${tagDOM}</li>`
        } else {
          nodeAttributesDOM += `<li>${item.column}</li>`
        }
      })
      //  <li>类型: ${event.item.getType()}</li>
      return `
              <ul class="node-edge-tooltip">
                <li class="tooltip-splider">${
                  event.item.getModel().keyAttr.column
                }：${event.item.getModel().value}条记录</li>
                <li class="tooltip-splider">节点属性</li>
                ${nodeAttributesDOM}
              </ul>
            `
    },
  })

  private edgeTooltip = new G6.Tooltip({
    itemTypes: ['edge'],
    offsetX: 25,
    offsetY: 0,
    getContent(event: any) {
      // 边属性 tooltip DOM
      let edgeAttributesDOM: string = ''
      event.item.getModel().attrs?.forEach((item: any) => {
        edgeAttributesDOM += `<li>${item.column}</li>`
      })
      if (event.item.getModel().attrs?.length === 0) {
        edgeAttributesDOM += '<li> -- 空 -- </li>'
      }
      // <li>Type: ${event.item.getType()}</li>
      return `
              <ul class="node-edge-tooltip">
                <li>边属性</li>
                ${edgeAttributesDOM}
              </ul>
            `
    },
  })

  /**
   * 点击选择边， 新建边， 将当前边存储在 store
   */
  public saveEdgeInfoToStore(edgeInfo: any) {
    GraphBuild.setEdgeSourceNode(edgeInfo.source) // 存储边的
    GraphBuild.setEdgeTargetNode(edgeInfo.target)
    GraphBuild.setSelectedEdge(edgeInfo)
  }

  private currentEdgeId = null

  // G6 graph 事件
  private handleGraphEvent() {
    this.modelGraph.on('node:mousedown', (event: any) => {
      if (!this.isGraphSelfCreate) {
        return
      }
      if (event.target.cfg.name === 'circle-anchor') {
        this.modelGraph.setMode('edit')
      }
    })

    this.modelGraph.on('canvas:mouseleave', () => {
      if (this.currentEdgeId !== null) {
        if (this.modelGraph.getCurrentMode() === 'edit') {
          this.modelGraph.removeItem(this.currentEdgeId, false)
        }
      } else {
        this.currentEdgeId = null
      }
    })

    this.modelGraph.on('node:mouseup', () => {
      if (this.modelGraph.getCurrentMode() === 'edit') {
        this.modelGraph.setMode('default')
      }
    })

    // 点击节点
    this.modelGraph.on('node:click', (event: any) => {
      const itemModel = event.item._cfg.model
      const nodeItem = event.item // 获取鼠标点击的节点元素对象
      // 没有 shift
      if (!event.originalEvent.shiftKey) {
        // 没有 ctrl
        if (!event.originalEvent.ctrlKey) {
          this.selectNodeEdge = nodeItem
          this.clearAllEdgeClickState() // 将所有选中的边置为未选中状态
          this.clearAllNodeStyle() // 先将所有节点样式置为未选中的状态
          this.clearAllEdgeSelectedState() // 清空框选状态的 的边
          GraphViewStore.setCurrentCategoryData(itemModel)
          GraphBuild.setCurrentCategoryData(itemModel) // 存储 当前节点

          const darkColor = getDarkColor(nodeItem._cfg.model.style.fill, 0.2)
          this.modelGraph.updateItem(nodeItem, {
            style: {
              stroke: darkColor,
            },
            showLinks: false,
          })
          this.$emit('nodeClick', {
            label: itemModel.label,
            tableName: itemModel.tableInfo.tableName,
          })
          // ctrl 多选
        } else {
          this.$emit('graphClick')
          this.ctrlSelectNodes.push(nodeItem) // ctrl 选的节点
          const darkColor = getDarkColor(nodeItem._cfg.model.style.fill, 0.2)
          this.modelGraph.updateItem(nodeItem, {
            style: {
              stroke: darkColor,
            },
            showLinks: false,
          })
        }
      }
    })
    // 鼠标进入节点 hover
    this.modelGraph.on('node:mouseenter', (event: any) => {
      this.addAttributeTooltip = false // 关闭伪造的 tooltip
      if (!this.isGraphSelfCreate) {
        return
      }
      const nodeItem = event.item // 获取鼠标进入的节点元素对象
      this.modelGraph.setItemState(nodeItem, 'hover', true) // 设置当前节点的 hover 状态为 true, (nodeStateStyles) 只是为了鼠标 cursor: 'pointer' 样式
      // 不在套索选中范围内的节点和为选中的当前节点， 展现 hover 阴影样式
      // const selectedIndex: any = this.brushLassoSelectNodes.findIndex((node: any) => node._cfg.id === nodeItem._cfg.id)
      // ctrl 多选
      const ctrlIndex: any = this.ctrlSelectNodes.findIndex(
        (node: any) => node._cfg.id === nodeItem._cfg.id
      )
      if (
        (!this.selectNodeEdge ||
          this.selectNodeEdge?._cfg.id !== nodeItem._cfg.id) &&
        ctrlIndex < 0
      ) {
        const lightColor = getLightColor(nodeItem._cfg.model.style.fill, 0.5)
        this.modelGraph.updateItem(nodeItem, {
          style: {
            stroke: lightColor,
          },
          showLinks: true,
        })
      }
    })
    // 鼠标离开节点 leave
    this.modelGraph.on('node:mouseleave', (event: any) => {
      const nodeItem = event.item
      this.modelGraph.setItemState(nodeItem, 'hover', false)
      // 不在套索选中范围内的节点和为选中的当前节点， 取消 hover 阴影样式
      // const selectedIndex: any = this.brushLassoSelectNodes.findIndex((node: any) => node._cfg.id === nodeItem._cfg.id)
      // ctrl 多选
      const ctrlIndex: any = this.ctrlSelectNodes.findIndex(
        (node: any) => node._cfg.id === nodeItem._cfg.id
      )
      if (
        (!this.selectNodeEdge ||
          this.selectNodeEdge?._cfg.id !== nodeItem._cfg.id) &&
        ctrlIndex < 0
      ) {
        this.modelGraph.updateItem(nodeItem, {
          style: {
            stroke: nodeItem._cfg.model.style.fill,
          },
          showLinks: false,
        })
      }
    })

    // edge 边的点击
    this.modelGraph.on('edge:click', (event: any) => {
      if (!this.isGraphSelfCreate) {
        return
      }
      const edgeItem = event.item // 获取被点击的边元素对象
      this.$emit('graphClick')
      if (!event.originalEvent.ctrlKey) {
        this.selectNodeEdge = edgeItem
        this.clearAllEdgeClickState() // 将所有选中的边置为未选中状态
        this.clearAllEdgeSelectedState() // 清空框选状态的 的边
        this.clearAllNodeStyle() // 清空所有节点的 样式
        GraphViewStore.setCurrentCategoryData(null)
        GraphBuild.setCurrentCategoryData(null)

        this.modelGraph.setItemState(edgeItem, 'click', true) // 设置当前边的 click 状态为 true
        // 先处理， 提取部分关于该边的数据  供应边配置抽屉
        const currentEdgeIdItem: any = cloneDeep(edgeItem)
        const saveStoreEdgeInfo: any = {
          model: currentEdgeIdItem._cfg.model,
          source: currentEdgeIdItem._cfg.source,
          target: currentEdgeIdItem._cfg.target,
        }
        this.saveEdgeInfoToStore(saveStoreEdgeInfo)
      } else {
        this.ctrlSelectEdges.push(edgeItem) // ctrl 选的节点
        this.modelGraph.setItemState(edgeItem, 'click', true) // 设置当前边的 click 状态为 true
      }
    })
    // 鼠标 edge 边 hover
    this.modelGraph.on('edge:mouseenter', (event: any) => {
      const edgeItem = event.item // 获取鼠标进入的 edge 元素对象
      this.modelGraph.setItemState(edgeItem, 'hover', true) // 设置当前 edge 的 hover 状态为 true
    })
    // 鼠标 edge 边 leave
    this.modelGraph.on('edge:mouseleave', (event: any) => {
      const edgeItem = event.item
      this.modelGraph.setItemState(edgeItem, 'hover', false)
    })

    // 节点拖拽结束
    this.modelGraph.on('node:dragend', (event: any) => {
      if (!this.isGraphSelfCreate) {
        return
      }
      if (event.target.cfg.name !== 'circle-anchor') {
        const modelItem = cloneDeep(event.item._cfg.model)
        // 拖拽的时候是 hover 状态 将 hover 状态的样式去除
        modelItem.style.stroke = modelItem.style.fill
        graphUpdateCategory({
          data: {
            graphId: this.getGraphId(),
            category: {
              ...modelItem,
            },
          },
        })
      }
    })

    // 创建边后
    this.modelGraph.on('aftercreateedge', (edgeCreateEvent: any) => {
      this.afterCreateEdge(edgeCreateEvent)
    })

    // 添加节点后
    this.modelGraph.on('afteradditem', (item: any, object: any) => {
      console.log('after add item', object)
      if (item.model.type === 'circle-running') {
        this.currentEdgeId = item.model.id
      }
      /**
       * TODO: call api to add new node
       */
    })

    // 删除 node 节点 / edge边 后
    this.modelGraph.on('afterremoveitem', () => {
      if (this.modelGraph.getCurrentMode() === 'edit') {
        this.modelGraph.setMode('default')
      }
    })

    // 点击画布
    this.modelGraph.on('canvas:click', () => {
      GraphViewStore.setCurrentCategoryData(null)
      GraphBuild.setCurrentCategoryData(null)
      this.edgeSetShow = false // 边的编辑框
      this.edgeWeightSetShow = false // 边的编辑框
      this.$emit('graphClick')
      // 先将所有当前是 click 状态的 节点/边 置为非 click 状态
      this.clearAllEdgeClickState() // 将所有选中的边置为未选中状态
      this.clearAllNodeStyle() // 清空所有节点的 样式
      this.clearAllEdgeSelectedState() // 清空框选状态的 的边
      this.selectNodeEdge = null // 清空记录的选的节点或边
    })

    // 当 click-select 选中的元素集合发生变化时将会触发下面时机事件，e 中包含相关信息
    // 框选 / 套索功能
    // this.modelGraph.on('nodeselectchange', ($event: any) => {
    //   this.clearAllNodeStyle()  // 清空所有节点的 样式
    //   // 当前操作后，所有被选中的 items 集合
    //   if($event.select) {
    //     const {nodes, edges} = $event.selectedItems
    //     this.brushLassoSelectNodes = nodes  // 框选/套索选中的节点
    //     this.brushLassoSelectEdges = edges  // 框选/套索选中的边
    //     nodes.forEach((nodeItem: any) => {
    //       const darkColor = getDarkColor(nodeItem._cfg.model.style.fill, 0.2)
    //       this.modelGraph.updateItem(nodeItem, {
    //         style: {
    //           stroke: darkColor,
    //           shadowBlur: 0,
    //           lineWidth: 4,
    //         }
    //       })
    //     })
    //   }
    // })
  }

  /**
   * 创建边封装
   */
  public afterCreateEdge(edgeCreateEvent: any) {
    // 切换到默认状态
    if (this.modelGraph.getCurrentMode() === 'edit') {
      this.modelGraph.setMode('default')
      this.currentEdgeId = null
    }
    const edgeData = edgeCreateEvent.edge.getModel()
    let sourceLabel = '' // 边的起始节点
    let targetLabel = '' // 边的结束节点
    this.modelGraph.getNodes().forEach((element: any) => {
      const node = element.getModel()
      if (node.id === edgeData.source) {
        sourceLabel = node.label
      }
      if (node.id === edgeData.target) {
        targetLabel = node.label
      }
    })
    const edgeLabel = `${sourceLabel}_${targetLabel}` // 边 label
    let type = 'line' // 默认指定直线
    // 判断自环边(起始节点 === 结束节点)
    if (edgeData.source === edgeData.target) {
      type = 'loop'
    }

    // 如果两点之间已经存在边， 则新建边为 quadratic / arc， 或者两点之间已经存在该方向边， 则不允许新建边
    let allowCreatEdge: boolean = true
    const filterCurrentEdgeId: any = this.modelGraph
      .getEdges()
      .filter((edge: any) => edge.getModel().id !== edgeData.id) // 过滤掉当前边
    // 判断是否存在两节点间的同相边
    filterCurrentEdgeId.forEach((edge: any) => {
      const { source, target } = edge.getModel()
      // 存在一条反向的边
      if (edgeData.source === target && edgeData.target === source) {
        type = 'quadratic' // 二阶贝塞尔曲线
      }
      // 存在一条同向的边
      if (edgeData.source === source && edgeData.target === target) {
        allowCreatEdge = false
      }
    })
    if (!allowCreatEdge) {
      this.$message.warning('不允许创建重复边！')
      this.$nextTick(() => {
        this.modelGraph.removeItem(edgeCreateEvent.edge)
      })
    } else {
      graphAddEdge({
        data: {
          graphId: this.getGraphId(),
          edge: {
            label: edgeLabel,
            type,
            labelCfg: {
              autoRotate: true,
            },
            source: edgeData.source,
            target: edgeData.target,
            attrs: [],
            style: {
              endArrow: false,
            },
          },
        },
      }).then((response) => {
        if (response.data.code === 100) {
          const newEdge = response.data.result
          this.modelGraph.updateItem(edgeCreateEvent.edge, {
            label: edgeLabel,
            type,
            id: newEdge.id,
            labelCfg: {
              autoRotate: true,
            },
            style: {
              endArrow: false,
            },
            attrIds: [],
          })
          // 自动打开边配置抽屉
          const saveStoreEdgeInfo: any = {
            model: cloneDeep(newEdge),
            source: cloneDeep(this.modelGraph.findById(newEdge.source)),
            target: cloneDeep(this.modelGraph.findById(newEdge.target)),
          }
          this.saveEdgeInfoToStore(saveStoreEdgeInfo)
        } else {
          // 后端禁止创建
          this.$nextTick(() => {
            this.modelGraph.removeItem(edgeCreateEvent.edge)
          })
        }
      })
    }
  }

  /**
   *  清空所有节点的 样式 （选中 style ）
   */
  public clearAllNodeStyle() {
    this.modelGraph.getNodes().forEach((node: any) => {
      this.modelGraph.updateItem(node, {
        style: { stroke: node._cfg.model.style.fill },
      })
    })
    this.ctrlSelectNodes = []
    // this.brushLassoSelectNodes =[]  // 框选/套索选中的节点
  }

  /**
   * 清空选择边 click 状态
   */
  public clearAllEdgeClickState() {
    GraphBuild.setSelectedEdge(null) // 清空 store 存储的边
    const clickEdges = this.modelGraph.findAllByState('edge', 'click')
    clickEdges.forEach((ce: any) => {
      this.modelGraph.setItemState(ce, 'click', false)
    })
  }

  /**
   * 清空选择边 selected 状态
   */
  public clearAllEdgeSelectedState() {
    // this.brushLassoSelectEdges = []  // 框选/套索选中的边
    const selectedEdges = this.modelGraph.findAllByState('edge', 'selected')
    selectedEdges.forEach((ce: any) => {
      this.modelGraph.setItemState(ce, 'selected', false)
    })
  }

  public get graphSize() {
    return GraphViewStore.graphSize
  }

  @Watch('graphSize')
  public updateGraphHeight() {
    if (this.modelGraph) {
      const graphElement = this.$refs.graphModelContainer as HTMLElement
      this.modelGraph.changeSize(
        graphElement.offsetWidth,
        graphElement.offsetHeight
      )
    }
  }

  @Watch('data')
  public showGraphModel() {
    const graphElement = this.$refs.graphModelContainer as HTMLElement
    this.modelGraph = new G6.Graph({
      // fitView: true,  // 是否开启画布自适应。开启后图自动适配画布大小。
      // fitViewPadding: 50,  // fitView 为 true 时生效图适应画布时，指定四周的留白
      // fitCenter: true,  // 开启后，图将会被平移，图的中心将对齐到画布中心，但不缩放。优先级低于 fitView
      // 节点在各状态下的样式
      nodeStateStyles: {
        // hover 状态为 true 时的样式
        hover: {
          cursor: 'pointer',
        },
        // TODO
        // selected: () => {
        //   return {}
        // }
      },
      edgeStateStyles: {
        hover: {
          lineWidth: 2,
          cursor: 'pointer',
          stroke: '#A8AEFF',
        },
        click: {
          lineWidth: 3,
          stroke: '#A8AEFF',
        },
        // 框选 / 套索
        selected: {
          lineWidth: 3,
          shadowBlur: 0,
          stroke: '#A8AEFF',
        },
      },
      container: 'graphModelContainer',
      width: graphElement.offsetWidth,
      height: graphElement.offsetHeight,
      plugins: [
        this.contextMenu,
        this.edgeContextMenu,
        this.nodeTooltip,
        this.edgeTooltip,
      ],
      modes: {
        default: ['drag-node', 'drag-canvas', 'zoom-canvas'],
        edit: [
          {
            type: 'create-edge',
            trigger: 'drag',
          },
        ],
      },
      defaultEdge: {
        type: 'circle-running',
        style: {
          lineWidth: 2,
          stroke: '#D9D9D9',
          endArrow: false,
        },
        labelCfg: {
          autoRotate: false,
        },
      },
      defaultNode: {
        style: {
          lineWidth: 4,
        },
      },
    })

    this.modelGraph.data(this.data)
    this.modelGraph.render()
    // TODO 暂时关闭关闭局部渲染， 解决 label edge 移动残影问题
    this.modelGraph.get('canvas').set('localRefresh', false)
    this.handleGraphEvent()
  }

  /**
   * 更新当前节点的表名，
   * 此处不应该只更新表明， 还应该更新 主属性， 附属性
   */
  public updateNodeTableById(info: any) {
    this.modelGraph.updateItem(info.categoryId, {
      attrs: info.category.attrs,
      keyAttr: info.category.keyAttr,
      originLabel: info.category.originLabel,
      tableInfo: info.category.tableInfo,
      value: info.category.value,
    })
  }

  // 添加节点的 自定义 tooltip 提示
  public addAttributeTooltip: boolean = false
  // 自定义 tooltip 信息
  public nodeTooltipInfo: any = {
    keyAttr: {},
    attrs: [],
    originLabel: {},
    x: '0px',
    y: '0px',
    value: 0,
    currentAdd: {},
  }

  /**
   * 添加节点属性， 伪造的 tooltip 显示
   */
  public addAttributeToNode(addInfo: any) {
    const { node, attr } = addInfo
    this.nodeTooltipInfo.keyAttr = node.keyAttr
    this.nodeTooltipInfo.attrs = node.attrs
    this.nodeTooltipInfo.originLabel = node.originLabel
    this.nodeTooltipInfo.value = node.value
    this.nodeTooltipInfo.currentAdd = attr // 当前的
    if (!this.addAttributeTooltip) {
      this.addAttributeTooltip = true
    }
    this.tooltipPosition(node)
    this.nodeTooltipDebounce() // 2000ms 关闭
  }

  /**
   * 伪造节点 tooltip 计算位置
   */
  public tooltipPosition(node: any) {
    this.$nextTick(() => {
      // 画布尺寸
      const width = this.modelGraph.get('width')
      const height = this.modelGraph.get('height')
      // tooltip dom 及 offsetX offsetY
      const tooltipRect: any = (this.$refs
        .addAttributeTooltipRef as Element).getBoundingClientRect()

      // 节点的包围盒
      const itemBBox = this.modelGraph.findById(node.id).getBBox()
      // 右下角位置点
      const point = {
        x: itemBBox.minX + itemBBox.width,
        y: itemBBox.minY + itemBBox.height,
      }
      // 将 pointX/pointY 坐标系的坐标值转换为 canvasX/canvasY 的坐标值。（涉及到缩放，平移）
      const { x, y } = this.modelGraph.getCanvasByPoint(point.x, point.y)
      const graphContainer = this.modelGraph.getContainer() // Graph 容器 DOM
      const nodeRadius: number = node.size // 节点尺寸
      let tooltipX: number = x + graphContainer.offsetLeft
      let tooltipY: number = y + graphContainer.offsetTop
      // x 超出调整
      if (x + tooltipRect.width > width) {
        tooltipX -= tooltipRect.width + nodeRadius
      }
      // y 超出调整
      if (y + tooltipRect.height > height) {
        tooltipY -= tooltipRect.height + nodeRadius
      }
      this.nodeTooltipInfo.x = `${tooltipX}px`
      this.nodeTooltipInfo.y = `${tooltipY}px`
    })
  }

  /**
   * 判断同一属性（keyAttr, attr[]  主属性是其中一个）
   */
  public attributeIsEqual(attribute: any, originLabel: any) {
    return isObjectValueEqual(attribute, originLabel)
  }

  // 防抖
  private nodeTooltipDebounce = debounce(
    () => this.closeAddAttributeTooltip(),
    2000
  )

  /**
   * 关闭 自定义 tooltip
   */
  public closeAddAttributeTooltip() {
    this.addAttributeTooltip = false
  }

  public updateSemanticById(category: any) {
    this.modelGraph.updateItem(category.id, {
      tableInfo: category.tableInfo,
    })
  }

  public findElementByOwnerIdFromG6(ownerId: any) {
    const graphEdges: any = cloneDeep(this.modelGraph.getEdges())
    return graphEdges.find((edge: any) => edge._cfg.model.id === ownerId)
  }

  /**
   * undo redo 操作,
   */
  public graphUndoRedo(resultInfo: any) {
    const {
      actionCategories,
      actionCategoryIds,
      actionEdgeIds,
      actionEdges,
      actionType,
    } = resultInfo.result
    const { type } = resultInfo
    const actionCategory = actionCategories[0]

    switch (actionType) {
      case 'update':
        actionCategories.forEach((node: any) => {
          this.modelGraph.updateItem(node.id, node)
        })
        actionEdges.forEach((edge: any) => {
          this.modelGraph.updateItem(edge.id, edge)
        })
        break
      case 'delete': {
        if (type === 'undo') {
          actionCategories.forEach((node: any) => {
            this.modelGraph.addItem('node', {
              ...node,
              type: 'graph-model-node',
            })
          })
          actionEdges.forEach((edge: any) => {
            this.modelGraph.addItem('edge', edge)
          })
        } else if (type === 'redo') {
          actionCategoryIds.forEach((nodeId: any) => {
            this.modelGraph.removeItem(nodeId)
          })
          actionEdgeIds.forEach((edgeId: any) => {
            const findEdgeByOwnerId: any = this.findElementByOwnerIdFromG6(
              edgeId
            )
            if (findEdgeByOwnerId) {
              this.modelGraph.removeItem(findEdgeByOwnerId._cfg.id)
            } else {
              this.modelGraph.removeItem(edgeId)
            }
          })
        }
        break
      }
      case 'add':
        if (type === 'undo') {
          actionCategoryIds.forEach((nodeId: any) => {
            this.modelGraph.removeItem(nodeId)
          })
          actionEdgeIds.forEach((edgeId: any) => {
            // TODO 边的id 不同步
            const findEdgeByOwnerId: any = this.findElementByOwnerIdFromG6(
              edgeId
            )
            if (findEdgeByOwnerId) {
              this.modelGraph.removeItem(findEdgeByOwnerId._cfg.id)
            } else {
              this.modelGraph.removeItem(edgeId)
            }
          })
        } else if (type === 'redo') {
          actionCategories.forEach((node: any) => {
            this.modelGraph.addItem('node', {
              ...node,
              type: 'graph-model-node',
            })
          })
          actionEdges.forEach((edge: any) => {
            this.modelGraph.addItem('edge', edge)
          })
        }
        break
      case 'edge_conf':
        this.$message.success('节点连接配置已更改！')
        this.$emit('update-edge-configure') // 如果 边 setting 是打开的 ， 更新配置信息
        break
      case 'clean_add':
        this.updateNodeTableById({
          categoryId: actionCategory.id,
          category: actionCategory,
        })
        GraphViewStore.setCurrentCategoryData(actionCategory) // 将新节点存储
        GraphBuild.setCurrentCategoryData(actionCategory) // 将新节点存储
        GraphBuild.getHistoryList(this.getGraphId())
        break
      // case 'clean_update':
      //   GraphBuild.getHistoryList(this.getGraphId())
      //   break
      // case 'clean_delete':
      //   GraphBuild.getHistoryList(this.getGraphId())
      //   break
      default:
    }
  }

  /**
   * 右侧边配置更改成功后， 更新边的配置
   * @param config
   */
  public updateEdgeConnectionConfig(config: any) {
    const { edgeId, leftHeaderName, rightHeaderName } = config
    // g6 新创建的边的 id 为 g6 内部的id, 需要与后端给到的Id 做匹配， 仅限于新创建的边
    const findNode: any = this.modelGraph.find('edge', (edge: any) => {
      return edge.get('model').id === edgeId
    })
    this.modelGraph.updateItem(findNode, {
      joinConfigure: { leftHeaderName, rightHeaderName },
    })
  }
}
</script>

<style scoped lang="less">
@import '@/components/graph/graph-model';
</style>
